using System;
using System.IO;
using System.ServiceModel;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.Configuration;
using System.Data.SqlClient;
using System.Data;
using System.Data.SqlTypes;
using System.Reflection;
using System.Xml;
using Microsoft.MSE.Catalog.Contracts;
using Microsoft.MSE.Catalog.Module;


namespace Microsoft.MSE.Catalog.Services
{

    public class WCFCatalogSvcHost {

        public static ServiceHost serviceHost = null;
        private static SqlConnection sConn;
        private static string sNotificationAddress = null;
        private static bool m_CancelStartup = false;

        #region Service Control Methods
        public static event EventHandler<StartServiceCompletedEventArgs> StartServiceCompleted;
        protected static void OnStartServiceCompleted(StartServiceCompletedEventArgs e)
        {
            try
            {
                if (StartServiceCompleted != null)
                {
                    StartServiceCompleted(null, e);
                }
            }
            catch { }
        }
        private delegate void StartServiceAsyncHandler(int retryInterval, int retryAttempts);
        public static void StartServiceAsync()
        {
            m_CancelStartup = false;
            Configurator.ReadConfig();

            int delay = Configurator.svcConfig.StartupRetryInterval;
            int attempts = Configurator.svcConfig.StartupRetryAttempts;
            StartServiceAsyncHandler hnd = new StartServiceAsyncHandler(InnerStartService);
            hnd.BeginInvoke(delay, attempts, delegate(IAsyncResult ar)
            {
                StartServiceCompletedEventArgs args = null;
                try
                {
                    StartServiceAsyncHandler state = ar.AsyncState as StartServiceAsyncHandler;
                    if (state != null)
                        state.EndInvoke(ar);
                    args = new StartServiceCompletedEventArgs(null, m_CancelStartup, null);
                }
                catch (System.Exception ex)
                {
                    args = new StartServiceCompletedEventArgs(ex, m_CancelStartup, null);
                }
                OnStartServiceCompleted(args);

            }, hnd);
        }
        public static void StartService(){
            m_CancelStartup = false;
            Configurator.ReadConfig();

            InnerStartService(0, 0);
        }
        private static void InnerStartService(int retryInterval, int retryAttempts) {

            sConn = new SqlConnection(Configurator.svcConfig.DBConnString);

            //if we can't connect to the repository, we are finished
            if (!TryConnectToRepository(retryInterval, retryAttempts))
            {
                throw new ApplicationException("Could not establish connection to the Repository.");
            }

            WCFCatalogSvc catalogSvc = new WCFCatalogSvc(Configurator.svcConfig.baseAddress);
            serviceHost = new ServiceHost(catalogSvc, Configurator.svcConfig.baseAddress);

            ServiceMetadataBehavior bv = new ServiceMetadataBehavior();
            bv.HttpGetEnabled = true;
            serviceHost.Description.Behaviors.Add(bv);

            foreach (ServiceEndpointElement endpoint in Configurator.svcConfig.serviceEndpoints) {
                Binding binding = null;
                Type contract = null;
                binding = BindingHelper.DeserializeBinding((XmlElement)endpoint.bindingXml);
                if (endpoint.address.AbsoluteUri.EndsWith("/mex"))
                {
                    contract = typeof(IMetadataExchange);
                }
                else if (endpoint.address.AbsoluteUri.EndsWith("/RepositoryChanges"))
                {
                    contract = typeof(IRepositoryChangedSvc);
                    sNotificationAddress = endpoint.address.AbsoluteUri;
                }
                else
                {
                    contract = typeof(IWCFCatalogSvc);
                    //if (binding is BasicHttpBinding)
                    //    sNotificationAddress = endpoint.address.AbsoluteUri;
                }
                               
                TraceHelper.TraceInformation(SC.ComponentName + " adding endpoint address " + endpoint.address + " over " + endpoint.bindingXml.FirstChild.Name);
                binding.ReceiveTimeout = new TimeSpan(1, 0, 0, 0, 0);  //TODO: Look at this
                serviceHost.AddServiceEndpoint(contract,binding,endpoint.address);
            }


            ServiceDebugBehavior sdbbv = null;
            foreach (object o in serviceHost.Description.Behaviors) {
                if (o is ServiceDebugBehavior) sdbbv = o as ServiceDebugBehavior;
            }

            if (sdbbv == null) {
                sdbbv = new ServiceDebugBehavior();
                serviceHost.Description.Behaviors.Add(sdbbv);
            }


            sdbbv.HttpHelpPageEnabled = true;

            if (Configurator.svcConfig.DebugMode) {
                sdbbv.IncludeExceptionDetailInFaults = true;
            }

            // Add ServiceThrottlingBehavior
            ServiceThrottlingBehavior stb = SetupServiceCatalogThrottle(Configurator.svcConfig.serviceCatalogThrottle);
            if (null != stb)
            {
                ServiceThrottlingBehavior stdStb = serviceHost.Description.Behaviors.Find<ServiceThrottlingBehavior>();
                if (null != stdStb)
                {
                    serviceHost.Description.Behaviors.Remove<ServiceThrottlingBehavior>();
                }
                serviceHost.Description.Behaviors.Add(stb);
            }


            serviceHost.Open();

            RegisterForEvent(DBEvents.EndpointsUpdated);
            RegisterForEvent(DBEvents.EndpointPolicyUpdated);
            RegisterForEvent(DBEvents.ServiceDefinitionUpdated);
            RegisterForEvent(DBEvents.ServicePolicyUpdated);
            RegisterForEvent(DBEvents.OperationsUpdated);
            RegisterForEvent(DBEvents.RuntimeServersUpdated);
            RegisterForEvent(DBEvents.EndpointOpAssociationUpdated);
            RegisterForEvent(DBEvents.EndpointOpPublicationUpdated);
        }

        public static void StopService() {
            m_CancelStartup = true;
            DeregisterForEvent(DBEvents.EndpointsUpdated);
            DeregisterForEvent(DBEvents.EndpointPolicyUpdated);
            DeregisterForEvent(DBEvents.ServiceDefinitionUpdated);
            DeregisterForEvent(DBEvents.ServicePolicyUpdated);
            DeregisterForEvent(DBEvents.OperationsUpdated);
            DeregisterForEvent(DBEvents.RuntimeServersUpdated);
            DeregisterForEvent(DBEvents.EndpointOpAssociationUpdated);
            DeregisterForEvent(DBEvents.EndpointOpPublicationUpdated);

            if (serviceHost != null) {
                if (serviceHost.State != CommunicationState.Closed) serviceHost.Close();
            }

        }
        #endregion

        private static bool TryConnectToRepository(int retryInterval, int retryAttempts)
        {
            bool result = false;
            bool autoclose = false;

            if (sConn.State == ConnectionState.Closed)
                autoclose = true;

            //attempts = 1 + # of retries configured. 0 retries = 1 attempt
            int attempts = retryAttempts <= 0 ? 1 : retryAttempts + 1;

            while (!m_CancelStartup && attempts-- >= 0)
            {
                try
                {
                    sConn.Open();
                    result = true;
                    break;
                }
                catch (System.Exception ex)
                {
                    TraceHelper.TraceError("Unable to connect to repository. Exception: " + ex.Message);
                }

                //stop if there is no delay specified (only try once), or we have run out of attempts
                if (retryInterval <= 0 || attempts <= 0)
                {
                    TraceHelper.TraceError("MSE Catalog could not connect to Repository.  No further attempts will be made.");
                    break;
                }

                TraceHelper.TraceInformation("MSE Catalog will attempt to connect to Respository in {0} seconds. {1} attempt(s) remaining.", retryInterval, attempts);

                //configuration value is in seconds, convert to msec for thread sleep.
                System.Threading.Thread.Sleep(retryInterval * 1000);

            }
            
            if (result && autoclose)
                sConn.Close();
         
            return result;
        }
        private static void RegisterForEvent(DBEvents dbEvent) {
            if (string.IsNullOrEmpty(sNotificationAddress)) {
                TraceHelper.TraceError("Failed to register with CatalogDB for Event Notification - No BasicHTTPBinding specified");
                return;
            }

            bool autoclose = false;
            try {
                if (sConn.State == ConnectionState.Closed) {
                    autoclose = true;
                    sConn.Open();
                }

                using (SqlCommand cmd = new SqlCommand("[ServiceEngine].[SubscribeToEvent]", sConn)) {
                    SqlParameter pEventname = new SqlParameter("@Eventname", SqlDbType.VarChar, 100);
                    SqlParameter pCallbackURI = new SqlParameter("@CallbackURI", SqlDbType.VarChar, 200);
                    SqlParameter pExpires = new SqlParameter("@Expires", SqlDbType.DateTime);
                    pExpires.Direction = ParameterDirection.Output;

                    cmd.CommandType = CommandType.StoredProcedure;
                    pEventname.Value = Enum.GetName(typeof(DBEvents),dbEvent);
                    pCallbackURI.Value = sNotificationAddress;
                    cmd.Parameters.Add(pEventname);
                    cmd.Parameters.Add(pCallbackURI);
                    cmd.Parameters.Add(pExpires);

                    cmd.ExecuteNonQuery();

                    if (!(((SqlDateTime)pExpires.SqlValue).IsNull)) {

                        string expires = ((DateTime)pExpires.Value).ToString();

                    } else {
                        TraceHelper.TraceError("Failed to register with CatalogDB for Event Notification - Response from CatalogDB was null expiration");
                    }
                }
            } finally {
                if (autoclose) sConn.Close();
            }

        }

        private static void DeregisterForEvent(DBEvents dbEvent) {
            if (string.IsNullOrEmpty(sNotificationAddress)) {
                return;
            }

            bool autoclose = false;
            try {
                if (sConn.State == ConnectionState.Closed) {
                    autoclose = true;
                    sConn.Open();
                }
                using (SqlCommand cmd = new SqlCommand("[ServiceEngine].[UnsubscribeFromEvent]", sConn)) {
                    SqlParameter pEventname = new SqlParameter("@Eventname", SqlDbType.VarChar, 100);
                    SqlParameter pCallbackURI = new SqlParameter("@CallbackURI", SqlDbType.VarChar, 200);

                    cmd.CommandType = CommandType.StoredProcedure;
                    pEventname.Value = Enum.GetName(typeof(DBEvents), dbEvent);
                    pCallbackURI.Value = sNotificationAddress;
                    cmd.Parameters.Add(pEventname);
                    cmd.Parameters.Add(pCallbackURI);

                    cmd.ExecuteNonQuery();
                }
            } finally {
                if (autoclose) sConn.Close();
            }

        }
        private static ServiceThrottlingBehavior SetupServiceCatalogThrottle(ServiceCatalogThrottle serviceCatalogThrottle)
        {
            ServiceThrottlingBehavior stb = new ServiceThrottlingBehavior();
            stb.MaxConcurrentCalls = 0 == serviceCatalogThrottle.MaxConcurrentCalls ? 100 : serviceCatalogThrottle.MaxConcurrentCalls;
            stb.MaxConcurrentSessions = 0 == serviceCatalogThrottle.MaxConcurrentSessions ? 100 : serviceCatalogThrottle.MaxConcurrentSessions;
            stb.MaxConcurrentInstances = 0 == serviceCatalogThrottle.MaxConcurrentInstances ? Int32.MaxValue : serviceCatalogThrottle.MaxConcurrentInstances;
            return stb;
        }
    }
}
